跳到主要内容

Docker 构建镜像

Dockerfile 是什么

Dockerfile 其实就是我们用来构建 Docker 镜像的源码,当然这不是所谓的编程源码,而是一些命令的组合,只要理解它的逻辑和语法格式,就可以编写 Dockerfile 了。

简单点说,Dockerfile 的作用:它可以让用户个性化定制 Docker 镜像。因为工作环境中的需求各式各样,网络上的镜像很难满足实际的需求。

说白了 Dockerfile 就是一个用来构建镜像的文本文件(就是一些打包脚本)

语法:

# -f 自定义的 dockerfile 文件,-t 生成的文件名,最后的 . 表示生成在当前目录下
docker build -f dockerfile01 -t "testimage" .

构建的工作原理

docker build -t kubia .

用户告诉 Docker 需要基于当前目录(注意这里结尾的点)构建一个叫 kubia 的镜像,Docker 会在目录中寻找 Dockerfile,然后构建镜像

镜像构建原理与上下文

如果注意,会看到 docker build 命令最后有一个 .。这个 . 表示当前目录,这个路径并不止是在指定 Dockerfile 所在路径,而是指上下文路径

首先我们要理解 docker build 的工作原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API。

而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。

备注

构建过程不是由 Docker 客户端进行的,而是将整个目录的文件上传到 Docker 守护进程并在那里进行的。

当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?

这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

如果在 Dockerfile 中这么写:

COPY ./package.json /app/

这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json。

因此,COPY 这类指令中的源文件的路径都是相对路径。这也是为什么 COPY ../package.json /app 或者 COPY /opt/xxxx /app 无法工作的原因,因为这些路径已经超出了上下文的范围,Docker 引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。

将 Dockerfile 放到了硬盘根目录去构建,结果发现 docker build 执行后,在发送一个几十 GB 的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让 docker build 打包整个硬盘,这显然是使用错误。

一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。

Dockerfile 常用命令

image.png

# 先标明基础镜像
FROM ubuntu
VOLUME ["volume01","volume02"]
CMD echo "生成成功!"
CMD /bin/bash

RUN 命令指令通常用于安装应用和软件包。一个 Dockerfile 中可以有许多个 RUN 命令。

# apt-get update 和 apt-get install 被放在一个 RUN 指令中执行,这样能够保证每次安装的是最新的包。
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

值得注意的是,一个 RUN 等效于一个 Shell,所以在执行下一个 RUN 时,上一个 RUN 的进程都已经结束了

CMD 命令

CMD 指令允许用户指定容器的默认执行的命令。此命令会在容器启动且 docker run 没有指定其他命令时运行。

即通过执行 docker run $image $other_command 启动镜像可以重载 CMD 命令。

下面是一个例子:

CMD echo "Hello world"

运行容器 docker run -it [image] 将输出:

Hello world

但当后面加上一个命令,比如 docker run -it [image] /bin/bash,CMD 会被忽略掉,命令 bash 将被执行:

Dockerfile 中只能有一条 CMD 命令,如果写了多条则最后一条生效,且 CMD 不支持接收 docker run 的参数。

ENTRYPOINT 入口程序

ENTRYPOINT 的 Exec 格式用于设置容器启动时要执行的命令及其参数,同时可通过CMD命令或者命令行参数提供额外的参数。

ENTRYPOINT 中的参数始终会被使用,这是与CMD命令不同的一点。下面是一个例子:

ENTRYPOINT ["/bin/echo", "Hello"]

当容器通过 docker run -it [image] 启动时,输出为:

Hello

而如果通过 docker run -it [image] CloudMan 启动,则输出为:

Hello CloudMan

将 Dockerfile 修改为:

ENTRYPOINT ["/bin/echo", "Hello"]  
CMD ["world"]

当容器通过 docker run -it [image] 启动时,输出为:

Hello world

添加变量

通过 ARG 可以接受外部传入的变量

ARG TEST01=100
ARG TEST02=200

传入参数

docker build 命令传入参数

# 目标镜像版本号
VERSION=v1.0.0
# 目标镜像
TARGET_IMAGE=docker2.xxxx.com:5000/centos7/tensorflow/tf-nn-model:${VERSION}
# 构建目标镜像
sudo docker build --build-arg BASE_IMAGE=tensorflow-cpu/tf-cpu:vpb35 -t ${TARGET_IMAGE} .
# --build-arg

使用 Dockerfile 制作微服务镜像

利用 Dockerfile 制作一个 Eureka 注册中心的镜像 1、上传 Eureka 的微服务 jar 包到 Linux

多模块的项目不能把打包插件放到父级 Pom 中,只需在你子模块的 SpringBoot 独立运行的项目模块中依赖即可,如果是公共依赖的子模块(比如我项目中的 core 模块是一个公共依赖)也不需要依赖这个打包插件(否则会因为找不到 main 函数报错)。

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

2、编写 Dockerfile

# 使用的继承镜像
FROM openjdk:8-jdk-alpine
# 设置编译镜像时加入的参数
ARG JAR_FILE
# 编译镜像时复制文件到镜像中(第一个参数是 jar 包名称,第二个参数是移动到镜像后的名字)
COPY ${JAR_FILE} app.jar
# 设置镜像暴露的端口
EXPOSE 8761
# 设置容器的入口程序
ENTRYPOINT ["java","-jar","/app.jar"]

3、构建镜像

sudo docker build --build-arg JAR_FILE=EurekaServer-1.0-SNAPSHOT.jar -t eureka:v1 .

4、查看镜像是否创建成功

sudo docker images

5、创建容器

sudo docker run -i --name=eureka -p 8761:8761 eureka:v1

6、访问容器

192.168.211.129:8761

设置构建时的网络

注意,Docker 构建时候的网络模式可以另外设置

docker build –network=host

有三种网络可供选择

  • none
  • host
  • default:按照宿主机的网络模式

在 Dockerfile 指定构建配置